//
// Created by gd on 2019/11/30.
//
#include "peer.h"
#include <stdlib.h>
#include <thread>
#include <unistd.h>

#define SAMPLE_RATE  (8000)
#define FRAMES_PER_BUFFER (160)
#define NUM_SECONDS     (5)
#define NUM_CHANNELS    (1)
#define PA_SAMPLE_TYPE  paInt16

typedef short SAMPLE;

typedef struct {
  int frameIndex;  /* Index into sample array. */
  int maxFrameIndex;
  SAMPLE *recordedSamples;
  std::string dst;
  Peer* peer;
} paUserData;

Peer::Peer() {
}

gsm handle = gsm_create();
gsm_frame buf;
FILE *fid = fopen("recorded.gsm", "wba");

int recordCallback(const void *inputBuffer,
                   void *outputBuffer,
                   unsigned long framesPerBuffer,
                   const PaStreamCallbackTimeInfo *timeInfo,
                   PaStreamCallbackFlags statusFlags,
                   void *userData) {
  paUserData *data = (paUserData *) userData;
  const SAMPLE *rptr = (const SAMPLE *) inputBuffer;
  long framesToCalc;
  int finished;
  unsigned long framesLeft = data->maxFrameIndex - data->frameIndex;

  (void) outputBuffer; /* Prevent unused variable warnings. */
  (void) timeInfo;
  (void) statusFlags;
  (void) userData;

  if (framesLeft < framesPerBuffer) {
    framesToCalc = framesLeft;
    finished = paComplete;
  } else {
    framesToCalc = framesPerBuffer;
    finished = paContinue;
  }

  ::gsm_encode(handle, (short *) rptr, buf);
  fwrite(buf, 1, 33, fid);
  data->peer->tp->send(data->dst.c_str(), 1010, buf, 33);
  data->frameIndex += framesToCalc;
  return finished;
}

void Peer::sendVoice(const std::string dst) {
  PaError err = paNoError;
  PaStream *stream;
  PaStreamParameters inputParameters;
  paUserData data;
  data.dst = dst;
  data.peer = this;
  int i;
  int totalFrames;
  int numSamples;
  int numBytes;

  data.maxFrameIndex = totalFrames = NUM_SECONDS * SAMPLE_RATE; /* Record for a few seconds. */
  data.frameIndex = 0;
  numSamples = totalFrames * NUM_CHANNELS;
  numBytes = numSamples * sizeof(SAMPLE);
  data.recordedSamples = (SAMPLE *) malloc(numBytes); /* From now on, recordedSamples is initialised. */
  if (data.recordedSamples == nullptr) {
    printf("Could not allocate record array.\n");
    goto done;
  }
  for (i = 0; i < numSamples; i++) data.recordedSamples[i] = 0;

  err = Pa_Initialize();
  if (err != paNoError) goto done;

  inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
  if (inputParameters.device == paNoDevice) {
    fprintf(stderr, "Error: No default input device.\n");
    goto done;
  }
  inputParameters.channelCount = NUM_CHANNELS;
  inputParameters.sampleFormat = PA_SAMPLE_TYPE;
  inputParameters.suggestedLatency = Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency;
  inputParameters.hostApiSpecificStreamInfo = nullptr;

  err = Pa_OpenStream(
      &stream,
      &inputParameters,
      NULL,                  /* &outputParameters, */
      SAMPLE_RATE,
      FRAMES_PER_BUFFER,
      paClipOff,      /* we won't output out of range samples so don't bother clipping them */
      recordCallback,
      &data);
  if (err != paNoError) goto done;

  err = Pa_StartStream(stream);
  if (err != paNoError) goto done;
  printf("\n=== Now recording!! Please speak into the microphone. ===\n");
  fflush(stdout);
  while ((err = Pa_IsStreamActive(stream)) == 1) {
    Pa_Sleep(1000);
    printf("index = %d\n", data.frameIndex);
    fflush(stdout);
  }
  if (err < 0) goto done;

  err = Pa_CloseStream(stream);
  if (err != paNoError) goto done;

  done:
  Pa_Terminate();
  if (data.recordedSamples)       /* Sure it is NULL or valid. */
    free(data.recordedSamples);
  if (err != paNoError) {
    fprintf(stderr, "An error occured while using the portaudio stream\n");
    fprintf(stderr, "Error number: %d\n", err);
    fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(err));
  }
}

void voiceThread(Peer *peer) {
  while (peer->run_sche) {
    if (peer->remote_list.size() == 0) {
      continue;
    }
    int randomIndex = rand() % peer->remote_list.size();
    peer->sendVoice(peer->remote_list[randomIndex]);

    // redo after interval
    int interval = rand() % 9 + 2;
    sleep(interval);
  }
}

void textThread(Peer *peer) {
  while (peer->run_sche) {
    if (peer->remote_list.size() == 0) {
      continue;
    }
    int randomIndex = rand() % peer->remote_list.size();
    peer->sendText(peer->remote_list[randomIndex], "text");

    // redo after interval
    int interval = rand() % 9 + 2;
    sleep(interval);
  }
}

void Peer::runSche() {
  std::thread vt(voiceThread, this);
  std::thread tt(textThread, this);
  vt.join();
  tt.join();
}
void Peer::sendText(const std::string dst, const std::string text) {
  printf("sendto: %s", dst.c_str());
  this->tp->send(dst.c_str(), 2020, (void*)text.c_str(), text.size());
}
